package cn.chendd.compress;

/**
 * 7z格式的压缩和解压缩
 *
 * @author chendd
 * @date 2023/3/5 12:36
 */
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.archivers.sevenz.SevenZMethod;
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 7z压缩和解压缩
 * 注意：Apache Commons Compress 官网说明不支持7z格式的生成带密码格式的压缩
 *
 * @author chendd
 * @date 2022/11/18 9:28
 */
public class Compress7z {

    /**
     * 创建压缩文件
     * @param inFile 源文件
     * @param outFile 7z格式
     * @throws IOException 异常处理
     */
    public static void sevenZ(File inFile, File outFile) throws IOException {
        try (SevenZOutputFile szos = new SevenZOutputFile(outFile)) {
            szos.setContentCompression(SevenZMethod.DEFLATE);
            generare7zFile(inFile, szos, inFile);
            szos.finish();
        }
    }

    /**
     * 解压缩文件
     * @param src7zFile 源文件
     * @param outFile 输出文件
     */
    public static void unSevenZ(File src7zFile, File outFile) {
        unSevenZ(src7zFile, outFile, null);
    }

    /**
     * 根据密码解压7z文件
     * @param src7zFile 源文件
     * @param outFile 输出文件
     * @param password 密码
     */
    public static void unSevenZ(File src7zFile, File outFile, String password) {
        if (src7zFile == null || outFile == null) {
            throw new NullPointerException("file can be not null");
        }
        SevenZFile sevenZFile = null;
        try {
            if (!src7zFile.exists()) {
                throw new FileNotFoundException(src7zFile.getAbsolutePath());
            }
            if (StringUtils.isBlank(password)) {
                sevenZFile = new SevenZFile(src7zFile);
            } else {
                sevenZFile = new SevenZFile(src7zFile, password.toCharArray());
            }
            byte[] bytes = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
            SevenZArchiveEntry nextEntry = sevenZFile.getNextEntry();
            do {
                if (nextEntry == null) {
                    break;
                }
                if (nextEntry.isDirectory()) {
                    File file = new File(outFile, nextEntry.getName());
                    file.mkdirs();
                    continue;
                }
                File file = new File(outFile, nextEntry.getName());
                if (!file.getParentFile().exists()) {
                    file.getParentFile().mkdirs();
                }
                int lens;
                try (FileOutputStream outputStream = new FileOutputStream(file)) {
                    while ((lens = sevenZFile.read(bytes)) != -1) {
                        outputStream.write(bytes, 0, lens);
                    }
                }
            } while ((nextEntry = sevenZFile.getNextEntry()) != null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                IOUtils.close(sevenZFile);
            } catch (IOException ignore) {
            }
        }
    }

    /**
     * 生成7z压缩文件
     * @param inFile 源文件
     * @param szos 输出流
     * @param rootFile 根文件
     * @throws IOException 异常处理
     */
    private static void generare7zFile(File inFile, SevenZOutputFile szos, File rootFile) throws IOException {
        if (inFile.isFile()) {
            putEntryFile(szos, rootFile, inFile);
            return;
        }
        File[] files = inFile.listFiles();
        if (files == null || files.length == 0) {
            putEntryEmptyFolder(szos, inFile, rootFile);
            return;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                generare7zFile(file, szos, rootFile);
            } else {
                putEntryFile(szos, file, rootFile);
            }
        }
    }

    /**
     * 获取7z压缩包中的文件信息
     * 注意：
     * （1）未正常获取到压缩后的文件大小；
     * （2）与7z等软件的文件列表对比，当前的方法一次性罗列所有的文件夹以及子孙后代的所有文件；
     *
     * @param src7zFile 源7z文件
     * @param password  密码
     * @return 文件信息列表
     */
    public static List<View> view(File src7zFile, String password) {
        List<View> resultList = new ArrayList<>();
        try (SevenZFile sevenZFile = getSevenZFile(src7zFile, password)) {
            Iterable<SevenZArchiveEntry> entries = sevenZFile.getEntries();
            for (SevenZArchiveEntry entry : entries) {
                View view = new View();
                view.setDirectory(entry.isDirectory());
                view.setFileName(entry.getName());
                if (entry.getHasLastModifiedDate()) {
                    view.setLastModifiedTime(entry.getLastModifiedDate().getTime());
                } else if (entry.getHasCreationDate()) {
                    view.setLastModifiedTime(entry.getCreationDate().getTime());
                }
                view.setUncompressedSize(entry.getSize());
                long compressedSize = (long) MethodUtils.invokeMethod(entry, true, "getCompressedSize");
                view.setCompressedSize(compressedSize);
                resultList.add(view);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return resultList;
    }

    /**
     * 预览压缩文件列表
     * @param src7zFile 源文件
     * @return 文件列表
     */
    public static List<View> view(File src7zFile) {
        return view(src7zFile, null);
    }

    /**
     * 根据源文件和密码构造7z文件对象
     *
     * @param srcFile  源文件
     * @param password 密码
     * @return 7z文件对象
     * @throws IOException 异常处理
     */
    private static SevenZFile getSevenZFile(File srcFile, String password) throws IOException {
        if (StringUtils.isEmpty(password)) {
            return new SevenZFile(srcFile);
        }
        return new SevenZFile(srcFile, password.toCharArray());
    }

    /**
     * 压缩至文件
     * @param szos 输出流
     * @param file 文件
     * @param rootFile 根文件
     * @throws IOException 异常处理
     */
    private static void putEntryFile(SevenZOutputFile szos, File file, File rootFile) throws IOException {
        SevenZArchiveEntry archiveEntry = new SevenZArchiveEntry();
        boolean directory = file.isDirectory();
        String path = StringUtils.substringAfter(file.getAbsolutePath(), rootFile.getAbsolutePath() + File.separator);
        if (StringUtils.isBlank(path)) {
            archiveEntry.setName(file.getName());
        } else {
            archiveEntry.setName(path);
        }
        archiveEntry.setDirectory(directory);
        szos.putArchiveEntry(archiveEntry);
        try (InputStream inputStream = new FileInputStream(file)) {
            szos.write(inputStream);
        }
        szos.closeArchiveEntry();
    }

    /**
     * 压缩生成空文件夹
     * @param szos 输出流
     * @param file 文件
     * @param rootFile 根文件
     * @throws IOException 异常处理
     */
    private static void putEntryEmptyFolder(SevenZOutputFile szos, File file, File rootFile) throws IOException {
        SevenZArchiveEntry archiveEntry = new SevenZArchiveEntry();
        boolean directory = file.isDirectory();
        String path = StringUtils.substringAfter(file.getAbsolutePath(), rootFile.getAbsolutePath() + File.separator);
        if (StringUtils.isBlank(path)) {
            archiveEntry.setName(file.getName());
        } else {
            archiveEntry.setName(path);
        }
        archiveEntry.setDirectory(directory);
        szos.putArchiveEntry(archiveEntry);
        szos.closeArchiveEntry();
    }
}